﻿using System;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data.Utility;

namespace RayDen.Library.Components.Surface.Measured
{
    public class RegularHalfangleBRDF : SurfaceBsdf
    {
        public const int BRDF_SAMPLING_RES_THETA_H = 90;
        public const int BRDF_SAMPLING_RES_THETA_D = 90;
        public const int BRDF_SAMPLING_RES_PHI_D = 360;

        public const double RED_SCALE = (1.0 / 1500.0);
        public const double GREEN_SCALE = (1.15 / 1500.0);
        public const double BLUE_SCALE = (1.66 / 1500.0);
        // RegularHalfangleBRDF= Public Methods
        public RegularHalfangleBRDF(double[] d, int nth = 90, int ntd = 90,
                                    int npd = 180)
        {
            brdf = d;
            nThetaH = nth;
            nPhiD = npd;
            nThetaD = ntd;
        }

        public RegularHalfangleBRDF(string fileName)
        {
            var data = FileHelper.Read(fileName, out nThetaH, out nThetaD, out nPhiD);
            brdf = data;
        }

        // RegularHalfangleBRDF Private Data
        double[] brdf;
        int nThetaH, nThetaD, nPhiD;

        public override void f(ref Vector WO, ref Vector WI, ref Normal N, ref RgbSpectrum in_fs, out RgbSpectrum fs)
        {
            CreateFrame(ref N);
            var lwo = WorldToLocal(ref WO);
            var lwi = WorldToLocal(ref WI);
            EvalBrdf(out fs, ref lwo, ref lwi);
        }

        private void EvalBrdf(out RgbSpectrum fs,ref  Vector wo, ref Vector wi)
        {

            Vector wh = wo + wi;

            if (wh.z < 0f)
            {
                wo = -wo;
                wi = -wi;
                wh = -wh;
            }
            if (wh.x.NearZero() && wh.y.NearZero() && wh.z.NearZero())
            {
                fs = RgbSpectrum.ZeroSpectrum();
                return;
            }
            wh = Vector.Normalize(wh);
            float whTheta = SphericalTheta(ref wh);
            float whCosPhi = CosPhi(ref wh), whSinPhi = SinPhi(ref wh);
            float whCosTheta = CosTheta(ref wh), whSinTheta = SinTheta(ref wh);
            Vector whx = new Vector(whCosPhi*whCosTheta, whSinPhi*whCosTheta, -whSinTheta);
            Vector why = new Vector(-whSinPhi, whCosPhi, 0);
            Vector wd = new Vector(Vector.Dot(ref wi, ref whx), Vector.Dot(ref wi, ref why), Vector.Dot(ref wi, ref wh));

            // Compute _index_ into measured BRDF tables
            float wdTheta = SphericalTheta(ref wd), wdPhi = SphericalPhi(ref wd);
            if (wdPhi > MathLab.M_PI) wdPhi -= MathLab.M_PI;

            // Compute indices _whThetaIndex_, _wdThetaIndex_, _wdPhiIndex_
            int whThetaIndex = REMAP(MathLab.Sqrt(Math.Max(0f, whTheta/(MathLab.M_PI/2f))),
                                     1f, nThetaH);
            int wdThetaIndex = REMAP(wdTheta, MathLab.M_PI/2f, nThetaD);
            int wdPhiIndex = REMAP(wdPhi, MathLab.M_PI, nPhiD);
            int index = wdPhiIndex + nPhiD*(wdThetaIndex + whThetaIndex*nThetaD);

            //try {

                /*int rIndex = index;
                int gIndex = index + 1;
                int bIndex = index + 2;*/
                
                int rIndex = index;
                int gIndex = index + BRDF_SAMPLING_RES_THETA_H*BRDF_SAMPLING_RES_THETA_D*BRDF_SAMPLING_RES_PHI_D/2;
                int bIndex = index + BRDF_SAMPLING_RES_THETA_H*BRDF_SAMPLING_RES_THETA_D*BRDF_SAMPLING_RES_PHI_D;
                
                /*
                int rIndex = 3*index;
                int gIndex = 3*index + 1;//BRDF_SAMPLING_RES_THETA_H * BRDF_SAMPLING_RES_THETA_D * BRDF_SAMPLING_RES_PHI_D / 2;
                int bIndex = 3*index + 2;//BRDF_SAMPLING_RES_THETA_H * BRDF_SAMPLING_RES_THETA_D * BRDF_SAMPLING_RES_PHI_D;
                */
                fs = new RgbSpectrum((float) (brdf[Math.Min(rIndex, brdf.Length-1)]*RED_SCALE),
                                     (float) (brdf[Math.Min(gIndex, brdf.Length-1)]*GREEN_SCALE),
                                     (float) (brdf[Math.Min(bIndex, brdf.Length-1)]*BLUE_SCALE));
                /*
            }
            catch (IndexOutOfRangeException ex)
            {
                fs = new RgbSpectrum(0f);
            }*/
        }


        public override void Sample_f(ref Vector wo, ref Normal N, ref Normal shadeN, ref RgbSpectrum in_f, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out BsdfSampleData result)
        {
            EvalParams(ref surfaceData);
            result = new BsdfSampleData() { Type = this.Type };
            CreateFrame(ref N);
            var wi = MC.CosineSampleHemisphere(u1, u2);
            if (wo.z < 0f) wi.z *= -1f;
            result.Pdf = Pdf(ref wo, ref wi, BxDFTypes.BSDF_ALL);
            EvalBrdf(out result.F, ref wo, ref wi );
            wi = LocalToWorld(ref wi);
            result.Wi = wi;
            //	return f(wo, *wi);
        }

        private int REMAP(float V, float max, float count)
        {
            return MathLab.Clamp((int)(V / max * count), 0, (int)count);
        }

        public override BrdfType Type
        {
            get { return BrdfType.Glossy; }
        }

        public override bool IsDiffuse()
        {
            return true;
        }

        public override bool IsSpecular()
        {
            return false;
        }
    };
}